// Copyright (c) 2025, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'dart:io';

import 'package:args/args.dart';
import 'package:native_test_helpers/native_test_helpers.dart';

void main(List<String> args) {
  final stopwatch = Stopwatch()..start();
  final parser = ArgParser()
    ..addFlag(
      'set-exit-if-changed',
      negatable: false,
      help: 'Return a non-zero exit code if any files were changed.',
    );
  final argResults = parser.parse(args);
  final setExitIfChanged = argResults['set-exit-if-changed'] as bool;

  final counts = Counts();

  updateManifests(counts);

  stopwatch.stop();
  final duration = stopwatch.elapsedMilliseconds / 1000.0;
  print(
    'Generated ${counts.generated} files (${counts.changed} changed) in '
    '${duration.toStringAsFixed(2)} seconds.',
  );
  if (setExitIfChanged && counts.changed > 0) {
    exit(1);
  }
}

class Counts {
  int generated = 0;
  int changed = 0;
}

void updateManifests(Counts counts) async {
  final packageUri = findPackageRoot('hooks_runner');
  final testDataUri = packageUri.resolve('test_data/');
  final testDataDirectory = Directory.fromUri(testDataUri);
  updateManifest(testDataDirectory, counts, allowPartialProjects: false);
  final all = testDataDirectory.listSync(recursive: true);
  all.whereType<Directory>().forEach(
    (e) => updateManifest(e, counts, allowPartialProjects: true),
  );
}

const denyList = [
  '.dart_tool',
  'pubspec.lock',
  'manifest',
  'README.md',
  '.gitignore',
];

/// These just modify other test projects.
///
/// Don't add pubspecs to manifests, they contain a different name due to all
/// being part of a big workspace. They have a pubspec to enable analysis of
/// the Dart code in them.
const partialProjects = [
  'native_add_add_source',
  'native_add_add_symbol',
  'native_add_break_build',
  'native_add_fix_build',
  'simple_link_change_asset',
];

void updateManifest(
  Directory directory,
  Counts counts, {
  required bool allowPartialProjects,
}) {
  final manifestFile = File.fromUri(directory.uri.resolve('manifest.yaml'));
  if (!manifestFile.existsSync()) {
    return;
  }
  final all = directory.listSync(recursive: true);
  final dirPath = directory.uri.toFilePath(windows: false);
  final files =
      all
          .whereType<File>()
          .where((f) {
            for (final denyString in [
              ...denyList,
              if (!allowPartialProjects) ...partialProjects,
              for (final partialProject in partialProjects) ...[
                '$partialProject/pubspec.yaml',
              ],
            ]) {
              if (f.uri.toFilePath(windows: false).contains(denyString)) {
                return false;
              }
            }

            return true;
          })
          .map(
            (e) => e.uri.toFilePath(windows: false).replaceFirst(dirPath, ''),
          )
          .toList()
        ..sort();

  var oldContent = '';
  if (manifestFile.existsSync()) {
    oldContent = manifestFile.readAsStringSync();
  }
  final newContent = header + files.map((e) => '- $e\n').join();
  final newContentNormalized = newContent.replaceAll('\r\n', '\n');
  final oldContentNormalized = oldContent.replaceAll('\r\n', '\n');
  if (newContentNormalized != oldContentNormalized) {
    manifestFile.writeAsStringSync(newContent);
    print('Generated ${manifestFile.uri} (content changed)');
    counts.changed++;
  }
  counts.generated++;
}

const header = '''
# The list of files to copy to a temporary folder to ensure running tests from
# a completely clean setup.
# Automatically generated by manifest_generator.dart.
''';
